using System;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using SCore.Util;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Util.Pool
{
    public class Pool : MonoSingleton<Pool>
    {
        /// <summary>
        /// stores the recycle bins and is used to populate the lookup Dictionaries at startup
        /// </summary>
        [HideInInspector] public List<TrashManRecycleBin> recycleBinCollection = new List<TrashManRecycleBin>();

        /// <summary>
        /// this is how often in seconds Pool should cull excess objects. Setting this to 0 or a negative number will
        /// fully turn off automatic culling. You can then use the TrashManRecycleBin.cullExcessObjects method manually if
        /// you would still like to do any culling.
        /// </summary>
        [Header("检测一次剔除时间")] public float cullExcessObjectsInterval = 10f;

        /// <summary>
        /// uses the GameObject instanceId as its key for fast look-ups
        /// </summary>
        Dictionary<string, TrashManRecycleBin> _addressableToRecycleBin = new Dictionary<string, TrashManRecycleBin>();

        [HideInInspector] public new Transform transform;


        #region MonoBehaviour

        void Awake()
        {
            transform = gameObject.transform;
            initializePrefabPools();
            // only cull if we have an interval greater than 0
            if (cullExcessObjectsInterval > 0)
                StartCoroutine(cullExcessObjects());

            SceneManager.activeSceneChanged += activeSceneChanged;
        }


        void activeSceneChanged(Scene oldScene, Scene newScene)
        {
            if (oldScene.name == null)
                return;

            for (var i = recycleBinCollection.Count - 1; i >= 0; i--)
            {
                if (!recycleBinCollection[i].persistBetweenScenes)
                    removeRecycleBin(recycleBinCollection[i]);
            }
        }

        #endregion


        #region Private

        /// <summary>
        /// coroutine that runs every couple seconds and removes any objects created over the recycle bins limit
        /// </summary>
        /// <returns>The excess objects.</returns>
        IEnumerator cullExcessObjects()
        {
            var waiter = new WaitForSeconds(cullExcessObjectsInterval);

            while (true)
            {
                for (var i = 0; i < recycleBinCollection.Count; i++)
                    recycleBinCollection[i].cullExcessObjects();

                yield return waiter;
            }
        }


        /// <summary>
        /// populats the lookup dictionaries
        /// </summary>
        void initializePrefabPools()
        {
            foreach (var recycleBin in recycleBinCollection)
            {
                if (recycleBin == null || String.IsNullOrEmpty(recycleBin.addressable))
                    continue;
                _addressableToRecycleBin.Add(recycleBin.addressable, recycleBin);
            }
        }

        /// <summary>
        /// internal coroutine for despawning after a delay
        /// </summary>
        /// <returns>The despawn after delay.</returns>
        /// <param name="go">Go.</param>
        /// <param name="delayInSeconds">Delay in seconds.</param>
        IEnumerator internalDespawnAfterDelay(GameObject go, float delayInSeconds)
        {
            yield return new WaitForSeconds(delayInSeconds);
            despawn(go);
        }

        #endregion


        #region Public

        /// <summary>
        /// tells Pool to start managing the recycle bin at runtime
        /// </summary>
        /// <param name="recycleBin">Recycle bin.</param>
        public static void manageRecycleBin(TrashManRecycleBin recycleBin)
        {
            // make sure we can safely add the bin!
            if (Instance._addressableToRecycleBin.ContainsKey(recycleBin.addressable))
            {
                Debug.LogError("Cannot manage the recycle bin because there is already a GameObject with the name (" +
                               recycleBin.addressable + ") being managed");
                return;
            }

            Instance.recycleBinCollection.Add(recycleBin);
            Instance._addressableToRecycleBin.Add(recycleBin.addressable, recycleBin);
        }

        /// <summary>
        /// stops managing the recycle bin optionally destroying all managed objects
        /// </summary>
        /// <param name="recycleBin">Recycle bin.</param>
        /// <param name="shouldDestroyAllManagedObjects">If set to <c>true</c> should destroy all managed objects.</param>
        public static void removeRecycleBin(TrashManRecycleBin recycleBin, bool shouldDestroyAllManagedObjects = true)
        {
            var recycleBinName = recycleBin.addressable;

            // make sure we are managing the bin first
            if (Instance._addressableToRecycleBin.ContainsKey(recycleBinName))
            {
                Instance._addressableToRecycleBin.Remove(recycleBinName);
                Instance.recycleBinCollection.Remove(recycleBin);
                recycleBin.clearBin(shouldDestroyAllManagedObjects);
            }
        }

        //移除所有
        public static void removeRecycleBin(string addressable)
        {
            // make sure we are managing the bin first
            if (Instance._addressableToRecycleBin.TryGetValue(addressable, out var recycleBin))
            {
                Instance._addressableToRecycleBin.Remove(addressable);
                Instance.recycleBinCollection.Remove(recycleBin);
                recycleBin.clearBin(true);
            }
        }

        /// <summary>
        /// pulls an object out of the recycle bin using the bin's name
        /// </summary>
        public static async Task<GameObject> spawn(string addressable, Action<GameObject> callback = null)
        {
            if (Instance._addressableToRecycleBin.TryGetValue(addressable, out var recycleBin))
            {
                var newGo = await recycleBin.spawn();
                if (newGo != null)
                {
                    newGo.SetActive(true);
                    callback?.Invoke(newGo);
                    return newGo;
                }

                return null;
            }
            else
            {
                TrashManRecycleBin trashManRecycleBin = new TrashManRecycleBin()
                {
                    imposeHardLimit = false, //存在上线
                    cullExcessPrefabs = true, //需要剔除
                    instancesToMaintainInPool = 3, //保留数量
                    cullInterval = 10, //剔除间隔
                    persistBetweenScenes = false, //持久化
                    addressable = addressable,
                    binLoadType = BinLoadType.ASync,
                };
                manageRecycleBin(trashManRecycleBin);
                return await spawn(addressable, callback);
            }
        }


        public static async Task<GameObject> spawnWithAutoDel(string addressable, float waitTime,
            Action<GameObject> callback = null)
        {
            var go = await spawn(addressable, callback);
            despawnAfterDelay(go, waitTime);
            return go;
        }

        /// <summary>
        /// sticks the GameObject back into it's recycle bin after a delay. If the GameObject has no bin it is destroyed.
        /// </summary>
        /// <param name="go">Go.</param>
        public static void despawnAfterDelay(GameObject go, float delayInSeconds)
        {
            if (go == null)
                return;

            Instance.StartCoroutine(Instance.internalDespawnAfterDelay(go, delayInSeconds));
        }

        public static void despawn(GameObject go)
        {
            if (go == null)
                return;

            var goName = go.name;
            if (!Instance._addressableToRecycleBin.ContainsKey(goName))
            {
                Debug.LogWarning($"{goName} is not in pool bin");
            }
            else
            {
                Instance._addressableToRecycleBin[goName].despawn(go);
                if (go.transform as RectTransform != null)
                    go.transform.SetParent(Instance.transform, false);
                else
                    go.transform.parent = Instance.transform;
            }
        }

        /// <summary>
        /// gets the recycle bin for the given GameObject name. Returns null if none exists.
        /// </summary>
        public static TrashManRecycleBin recycleBinForGameObjectName(string addressable)
        {
            if (Instance._addressableToRecycleBin.TryGetValue(addressable, out var bin))
            {
                return bin;
            }

            return null;
        }

        #endregion
    }
}